#import "MMathLink.h"
#import "MEnvironment.h"
#import "mathlink.h"
#import <objc/objc-class.h>
#import "SocketObserver.h"

#define LINK ((MLINK)_link)

#if MLREVISION >= 3
#define DEVICE_TYPE MLDEVICE_TYPE
typedef int MLInteger;
#else
typedef long MLInteger;
#endif

@implementation MMathLink

+ (id) link
{
	return [MMathLink linkWithEnvironment: [MEnvironment environment]];
}

+ (id) linkWithArgs: (NSString*) args
{
	return [MMathLink linkWithEnvironment: [MEnvironment environment] args: args];
}

+ (id) linkWithArgv: (NSArray*) argv
{
	return [MMathLink linkWithEnvironment: [MEnvironment environment] argv: argv];
}

+ (id) linkWithEnvironment: (MEnvironment*) env
{
	MLInteger err;
	MLINK link = MLLoopbackOpen((MLEnvironment) [env environment], &err);
	if(link == NULL)
		return nil;
	return [[[MMathLink alloc] initWithEnvironment: env link: link close: YES] autorelease];
}

+ (id) linkWithEnvironment: (MEnvironment*) env args: (NSString*) args
{
	MLInteger err;
	MLINK link = MLOpenString((MLEnvironment) [env environment], [args UTF8String], &err);
	if(link == NULL)
		return nil;
	return [[[MMathLink alloc] initWithEnvironment: env link: link close: YES] autorelease];
}

+ (id) linkWithEnvironment: (MEnvironment*) env argv: (NSArray*) argv
{
	unsigned int count = [argv count], i;
	
	char** args = (char**) malloc(sizeof(char*) * count);
	for(i = 0; i < count; i++)
		args[i] = (char*) [[[argv objectAtIndex: i] description] UTF8String];
	
	MLInteger err;
	MLINK link = MLOpenArgv((MLEnvironment) [env environment], args, args + count, &err);
	free(args);
	if(link == NULL)
		return nil;
	
	return [[[MMathLink alloc] initWithEnvironment: env link: link close: YES] autorelease];
}

- (id) initWithEnvironment: (MEnvironment*) env link: (void* /*MLINK*/) link close: (BOOL) close
{
	NSParameterAssert(env);
	NSParameterAssert(link);
	
	self = [super init];
	_env = [env retain];
	_link = link;
	_close = close;
	
	MLSetUserData(LINK, self, NULL);
	return self;
}

- (void) dealloc
{
	[self setDelegate: nil];
	[self setRunLoop: NULL];
	
	MLSetUserData(LINK, NULL, NULL);
	if(_close)
		MLClose(LINK);
	[_env release];
	[super dealloc];
}

- (MEnvironment*) environment
{
	return _env;
}

- (void* /*MLINK*/) link
{
	return _link;
}

- (BOOL) connect
{
	return MLConnect(LINK);
}

- (NSString*) name
{
	return [NSString stringWithUTF8String: MLName(LINK)];
}

- (void) setName: (NSString*) name
{
	MLSetName(LINK, [name UTF8String]);
}

- (int) selectFileDescriptor
{
	long devType = UNREGISTERED_TYPE, theSize = sizeof(devType);
	if(MLDeviceInformation(LINK, DEVICE_TYPE, &devType, &theSize) == 0)
		return -1;
	
	devinfo_selector fdType;
	switch(devType)
	{
		case UNIXPIPE_TYPE:
			fdType = PIPE_FD;
			break;
		case UNIXSOCKET_TYPE:
			fdType = SOCKET_FD;
			break;
		case SOCKET2_TYPE:
			fdType = SOCKET_FD;
			break;
		case UNIXSHM_TYPE:
			fdType = SHM_FD;
			break;
		default:
			return -1;
	}
	
	int fd;
	theSize = sizeof(fd);
	if(MLDeviceInformation(LINK, fdType, &fd, &theSize) == 0)
		return -1;
	
	return fd;
}

- (BOOL) transferToLink: (MMathLink*) theLink
{
	return MLTransferExpression(LINK, (MLINK) [theLink link]);
}

@end

@implementation MMathLink (MMathLinkPacketMethods)

- (unsigned long) nextPacket
{
	return MLNextPacket(LINK);
}

- (BOOL) newPacket
{
	return MLNewPacket(LINK);
}

- (BOOL) endPacket
{
	return MLEndPacket(LINK);
}

- (BOOL) handlePacket
{
	if([self isReady])
	{
		unsigned long thePacket = [self nextPacket];
		if([_delegate respondsToSelector: @selector(handlePacket:link:)])
			[_delegate handlePacket: thePacket link: self];
		[self newPacket];
		return YES;
	}
	return NO;
}

@end

@implementation MMathLink (MMathLinkGetterMethods)

- (BOOL) isReady
{
	return MLReady(LINK);
}

- (MExprType) type
{
	int mltype = MLGetType(LINK);
	switch(mltype)
	{
		case MLTKSTR:
			return eExprTypeString;
		case MLTKSYM:
			return eExprTypeSymbol;
		case MLTKINT:
			return eExprTypeInteger;
		case MLTKREAL:
			return eExprTypeReal;
		case MLTKFUNC:
			return eExprTypeFunction;
	}
	return eExprTypeString;
}

- (NSString*) string
{
	NSString* s = nil;
	const unsigned short* u;
	long len;
	
	if(MLGetUnicodeString(LINK, &u, &len) != MLTKERR)
	{
		s = [NSString stringWithCharacters: u length: len];
		MLDisownUnicodeString(LINK, u, len);
	}
	return s;
}

- (NSString*) symbol
{
	NSString* s = nil;
	const unsigned short* u;
	long len;
	
	if(MLGetUnicodeSymbol(LINK, &u, &len) != MLTKERR)
	{
		s = [NSString stringWithCharacters: u length: len];
		MLDisownUnicodeSymbol(LINK, u, len);
	}
	return s;
}

- (NSData*) data
{
	NSData* data = nil;
	const unsigned char* s;
	MLInteger len;
	
	if(MLGetByteString(LINK, &s, &len, 0) != MLTKERR)
	{
		data = [NSData dataWithBytes: s length: len];
		MLDisownByteString(LINK, s, len);
	}
	return data;
}

- (NSNumber*) number
{
	int rawType = MLGetRawType(LINK);
	switch(rawType)
	{
		case MLTK_CSHORT:
		{
			short s;
			if(MLGetShortInteger(LINK, &s))
				return [NSNumber numberWithShort: s];
			return nil;
		}
		case MLTK_CINT:
#if MLTK_CLONG != MLTK_CINT
		case MLTK_CLONG:
#endif
		{
			long l;
			if(MLGetLongInteger(LINK, &l))
				return [NSNumber numberWithLong: l];
			return nil;
		}
		case MLTK_CFLOAT:
		{
			float f;
			if(MLGetFloat(LINK, &f))
				return [NSNumber numberWithFloat: f];
			return nil;
		}
		case MLTK_CDOUBLE:
		case MLTK_CLONGDOUBLE:
		{
			double d;
			if(MLGetDouble(LINK, &d))
				return [NSNumber numberWithDouble: d];
			return nil;
		}
	}
	return nil;
}

- (int) integer
{
	int i;
	if(MLGetInteger(LINK, &i))
		return i;
	return 0;
}

- (double) real
{
	double d;
	if(MLGetDouble(LINK, &d))
		return d;
	return 0.0;
}

- (int) functionLength
{
	MLInteger argc;
	if(MLGetArgCount(LINK, &argc))
		return argc;
	return -1;
}

- (int) expressionsToGet
{
	MLInteger count;
	if(MLExpressionsToGet(LINK, &count))
		return count;
	return -1;
}

@end

@implementation MMathLink (MMathLinkPutterMethods)

- (BOOL) putString: (NSString*) string
{
	mlapi_result success;
	unichar* buffer = (unichar*) malloc(sizeof(unichar) * [string length]);
	
	[string getCharacters: buffer];
	success = MLPutUnicodeString(LINK, buffer, [string length]);
	free(buffer);
	
	return success;
}

- (BOOL) putSymbol: (NSString*) symbol
{
	mlapi_result success;
	unichar* buffer = (unichar*) malloc(sizeof(unichar) * [symbol length]);
	
	[symbol getCharacters: buffer];
	success = MLPutUnicodeSymbol(LINK, buffer, [symbol length]);
	free(buffer);
	
	return success;
}

- (BOOL) putData: (NSData*) data
{
	return MLPutByteString(LINK, (const unsigned char*) [data bytes], [data length]);
}

- (BOOL) putNumber: (NSNumber*) number
{
	if([number isRealNumber])
		return MLPutDouble(LINK, [number doubleValue]);
	else
		return MLPutLongInteger(LINK, [number longValue]);
}

- (BOOL) putInteger: (int) integer
{
	return MLPutInteger(LINK, integer);
}

- (BOOL) putReal: (double) real
{
	return MLPutDouble(LINK, real);
}

- (BOOL) putFunctionWithLength: (int) length
{
	return (MLPutType(LINK, MLTKFUNC) && MLPutArgCount(LINK, length));
}

- (BOOL) putFunction: (NSString*) name length: (int) length
{
	return [self putFunctionWithLength: length] && [self putSymbol: name];
}

- (BOOL) flush
{
	return MLFlush(LINK);
}

@end

@implementation MMathLink (MMathLinkErrorMethods)

- (long) error
{
	return MLError(LINK);
}

- (BOOL) setError: (long) error
{
	return MLSetError(LINK, error);
}

- (BOOL) clearError
{
	return MLClearError(LINK);
}

- (NSString*) errorMessage
{
	return [NSString stringWithUTF8String: MLErrorMessage(LINK)];
}

- (void) raiseError
{
	[NSException raise: @"MathLinkException" format: [self errorMessage]];
}

@end

static void messageHandler(MLINK link, int m1, int m2)
{
	MMathLink* mlink = (MMathLink*) MLUserData(link, NULL);
	unsigned char c = (unsigned char) m1;
	write(mlink->_messagePipe[1], &c, 1);
}

@implementation MMathLink (MMathLinkDelegateMethods)

- (id) delegate
{
	return _delegate;
}

- (void) setDelegate: (id) delegate
{
	[_delegate autorelease];
	_delegate = [delegate retain];
}

- (CFRunLoopRef) runLoop
{
	return [_linkObserver runLoop];
}

- (void) setRunLoop: (CFRunLoopRef) runLoop
{
	// FIXME check for errors
	if(runLoop != [self runLoop])
	{
		if(_linkObserver)
		{
			MLSetMessageHandler(LINK, (MLMessageHandlerObject) _oldMessageHandler);
			
			[_linkObserver release];
			_linkObserver = nil;
			[_messageObserver release];
			_messageObserver = nil;
			close(_messagePipe[0]);
			_messagePipe[0] = -1;
			close(_messagePipe[1]);
			_messagePipe[1] = -1;
		}
		
		if(runLoop)
		{
			pipe(_messagePipe);
			_oldMessageHandler = (void*) MLMessageHandler(LINK);
			MLSetMessageHandler(_link, (MLMessageHandlerObject) messageHandler);
			
			_linkObserver = [[SocketObserver alloc] initWithRunLoop: runLoop
				socket: [self selectFileDescriptor] delegate: self context: self];
			_messageObserver = [[SocketObserver alloc] initWithRunLoop: runLoop
				socket: _messagePipe[0] delegate: self context: self];
		}
	}
}

- (void) observeSocket: (int) theSocket context: (void*) theContext
{
	if(theSocket == _messagePipe[0])
	{
		unsigned char theMessage = 0;
		if(read(_messagePipe[0], &theMessage, 1) == 1)
		{
			if([_delegate respondsToSelector: @selector(handleMessage:link:)])
				[_delegate handleMessage: theMessage link: self];
		}
	}
	else // (theSocket == [self selectFileDescriptor])
	{
		[self handlePacket];
	}
}

@end

@implementation NSNumber (MMathLinkAdditions)

- (BOOL) isRealNumber
{
	const char* theType = [self objCType];
	if((*theType == _C_DBL) || (*theType == _C_FLT))
		return YES;
	return NO;
}

@end
